#!/usr/bin/env python
# coding=utf-8

import os
import sys
import re
import codecs
import pickle
import cPickle
from OffsetDump import KiwiDataManager

# set log
import CheckLog
log = CheckLog.get_file_log('VrDicCheck.log')


class DicItemParamError(Exception): pass

class DicItem:
	_folder = r''
	_kiwiDataManager = None
	""" """
	def __init__(self, linestr, father):
		self._linestr = linestr
		self._children = None
		self._father = father
		self._kiwiRecord = None
		self._kiwiFrame = None
		self._checked = False
		self._type = None
		# parse attributes from line string
		# 'Name	PinYin	NextLevelId	NextLevelInfo	Offset VoiceId	NextLevelNumber'
		pat = re.match( \
				r'^(\S+)\t([\w ]+)\t(0x[\dA-F]{8})\t(0x[\dA-F]{8})\t(0x[\dA-F]{8})\t(0x[\dA-F]{8})\t(0x[\dA-F]{4})\r\n$', \
				self._linestr)
		if not pat:
			log.warning(r'<String Match Warning> : %s : %s', self.get_father()['nextLevelId'], self.get_str()[:-2])
			log.warning(r'I try to match pinyin with "/"')
			pat = re.match( \
				r'^(\S+)\t([\w /]+)\t(0x[\dA-F]{8})\t(0x[\dA-F]{8})\t(0x[\dA-F]{8})\t(0x[\dA-F]{8})\t(0x[\dA-F]{4})\r\n$', \
				self._linestr)
			if pat:
				log.warning(r'Try match pinyin with "/" successfully')
			else:
				log.warning(r'<String Match Error> : %s : %s', self.get_father()['nextLevelId'], self.get_str())
		# remove '\r\n' at the end of each line
		self._linestr = self._linestr[:-2]
		if not pat:
			log.warning(r'<Item Line Format Error> : %s : %s', self.get_father()['nextLevelId'], linestr)
			raise DicItemParamError()
		self._name = pat.group(1)
		self._pinyin = pat.group(2)
		self._nextLevelId = pat.group(3)
		self._nextLevelInfo = pat.group(4)
		self._offset = pat.group(5)
		self._voiceId = pat.group(6)
		self._nextLevelNum = pat.group(7)
		self._attrs = { 
				1: self._name, 'name': self._name,
				2: self._pinyin, 'pinyin':self._pinyin,
				3: self._nextLevelId, 'nextLevelId': self._nextLevelId,
				4: self._nextLevelInfo, 'nextLevelInfo': self._nextLevelInfo,
				5: self._offset, 'offset': self._offset,
				6: self._voiceId, 'voiceId': self._voiceId,
				7: self._nextLevelNum, 'nextLevelNum': self._nextLevelNum
				}

	def get_str(self):
		return self._linestr
	
	def check(self):
		" Check the item itself only. \n"
		checker = DicItemChecker(self)
		r =  checker.check()
		if r:
			#log.debug(r'<No Error> : %s', self.get_str())
			pass
		else:
			log.warning(r'<Error> : %s : %s', self.get_father()['nextLevelId'], self.get_str())
		return (r, checker)
	
	def check_recursively(self):
		" Check recursively.\n"
		if self._checked:
			return True
		self._checked = True
		(result, checker) = self.check()
		# has checked another name of this item, do not to check children
		if checker.checked_another_name():
			return result
		# check children recursively
		for child in self.get_children():
			if not child.check_recursively():
				result = False
		return result

	def has_child(self):
		if self['nextLevelId'] != '0xFFFFFFFF' \
				or self['nextLevelInfo'] != '0x00000001' \
				or self['nextLevelNum'] != '0x0000':
			# check all the info should all say ' has next level'
			if not (self['nextLevelId'] != '0xFFFFFFFF' \
					and self['nextLevelInfo'] != '0x00000001' \
					and self['nextLevelNum'] != '0x0000'):
				log.warning('<Has Next Level Not Agreed Error> : %s : %s', self.get_father()['nextLevelId'], self._linestr)
			return True
		else:
			# check all the info should all say ' not have next level'
			if not (self['nextLevelId'] == '0xFFFFFFFF' \
					and self['nextLevelInfo'] == '0x00000001' \
					and self['nextLevelNum'] == '0x0000'):
				log.warning('<Has Next Level Not Agreed Error> : %s : %s', self.get_father()['nextLevelId'], self._linestr)
			return False

	def get_father(self):
		return self._father

	def get_children(self):
		if self._children != None:
			return self._children
		elif not self.has_child():
			return tuple()
		children_set = []
		filename = self['nextLevelId'][2:]+'.txt'
		filepath = os.path.join(self._folder, filename)
		# open file and read lines
		try:
			fp = codecs.open(filepath,encoding='gbk')
			lines = fp.readlines()
			fp.close()
		except IOError:
			log.warning(r'<File Open&Read Error> : %s : %s ', self.get_father()['nextLevelId'], self._linestr)
			return tuple()
		except UnicodeDecodeError:
			log.warning(r'<File Encoding Error> : %s : %s : %s', self.get_father()['nextLevelId'], self._linestr, filename)
			fp.close()
			return tuple()
		# parse each line to a DicItem, and add the DicItems to children_set
		for s in lines:
			if re.match(r'^\s*$',s):
				log.warning(r'<File Format Error> : Filename: %s', filename)
				continue
			try:
				dicItem = DicItem(s, self)
			except DicItemParamError:
				log.warning(r'<Get Child Error> : %s	FileName : %s', self._linestr, filename)
				continue
			children_set.append(dicItem)
		self._children = tuple(children_set)
		return self._children

	def __getitem__(self, attr):
		' Return a string.\n'
		return self._attrs[attr]

	def is_root(self):
		return False
	
	def _fetch_kiwi_record(self):
		# root, process in DicRootItem
		#if self.is_root():
		#	return None
		f = self.get_father()
		# root -> province
		if f.is_root():
			kiwiRecords = self._kiwiDataManager.fetch_record(self['name'])
			return kiwiRecords
		ff = f.get_father()
		# root -> province -> city
		if ff.is_root():
			kiwiRecords =  self._kiwiDataManager.fetch_record(f['name'], self['name'])
			return kiwiRecords
		fff = ff.get_father()
		# root -> province -> city -> town
		if fff.is_root():
			kiwiRecords = self._kiwiDataManager.fetch_record(ff['name'], f['name'], self['name'])
			return kiwiRecords
		ffff = fff.get_father()
		# root -> province -> city -> town -> road
		if ffff.is_root():
			kiwiRecords = self._kiwiDataManager.fetch_record(fff['name'], ff['name'], f['name'], self['name'])
			return kiwiRecords
		# error
		raise KeyError()
	
	def fetch_kiwi_record(self):
		if self._kiwiRecord != None:
			return self._kiwiRecord
		rds = self._fetch_kiwi_record()
		if len(rds) == 1:
			return rds[0]
		for r in rds:
			if r['offset'] == int(self['offset'], 16):
				if r['name'] != self['name']:
					log.error(r'<Fetch Kiwi Record Error> : Same name record error : %s', self.get_str())
				self._kiwiRecord = r
				return r
		log.warning(r'<Fetch Kiwi Record Error> : %s : %s', self.get_father()['offset'], self['offset'])
		raise KeyError()
	
	def fetch_kiwi_frame(self):
		if self._kiwiFrame != None:
			return self._kiwiFrame
		# root, process in DicRootItem
		#if self.is_root():
		#	return None
		f = self.get_father()
		# root -> province
		if f.is_root():
			self._kiwiFrame = self._kiwiDataManager.fetch_frame(self['name'])
			return self._kiwiFrame
		ff = f.get_father()
		# root -> province -> city
		if ff.is_root():
			self._kiwiFrame =  self._kiwiDataManager.fetch_frame(f['name'], self['name'])
			return self._kiwiFrame
		fff = ff.get_father()
		# root -> province -> city -> town
		if fff.is_root():
			self._kiwiFrame = self._kiwiDataManager.fetch_frame(ff['name'], f['name'], self['name'])
			return self._kiwiFrame
		ffff = fff.get_father()
		# error
		raise KeyError()
	
	def get_type(self):
		"Return 'Sheng', 'ZhiXiaShi', 'Di', 'Xian', 'QuanYu', 'Lu'\n"
		if self._type != None:
			return self._type
		if not self.has_child():
			self._type = 'Lu'
			return self._type
		if self.get_father().is_root():
			if self['name'] == u'上海市' or self['name'] == u'北京市' or \
					self['name'] == u'天津市' or self['name'] == u'重庆市':
				self._type = 'ZhiXiaShi'
			else:
				self._type = 'Sheng'
			return self._type
		if self['name'].endswith(u'全域'):
			self._type = 'QuanYu'
			return self._type
		fType = self.get_father().get_type()
		if fType == 'ZhiXiaShi':
			self._type = 'Qu'
			return self._type
		elif fType == 'Sheng':
			if len(self.get_children())==0:
				log.error(r'<Children Len Error> : %s', self.get_str())
			if self.get_children()[0].has_child():
				self._type = 'Di'
				return self._type
			else:
				self._type = 'Xian'
				return self._type
		self._type = 'Xian'
		return self._type


class DicRootItem(DicItem):
	def __init__(self):
		linestr = 'China	NoPinYin	0x80000001	0x00000000	0x00000000	0x00000000	0x0000\r\n'
		DicItem.__init__(self, linestr, None)
		
	def check(self):
		pass
	
	def check_recursively(self):
		" Check recursively.\n"
		if self._checked:
			return True
		self._checked = True
		# check children recursively
		result = True
		for child in self.get_children():
			if not child.check_recursively():
				result = False
		return result
	
	def has_child(self):
		return True
	
	def is_root(self):
		return True
	
	def fetch_kiwi_record(self):
		return None
	
	def fetch_kiwi_frame(self):
		if self._kiwiFrame != None:
			return self._kiwiFrame
		self._kiwiFrame = self._kiwiDataManager.fetch_frame()
		return self._kiwiFrame
	
	def get_type(self):
		return 'China'


class DicItemChecker:
	"""Class to check dictionary item. 
	"""
	# alphabets that might be found in text.
	alphabets = set(u'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZａｂｃｄｅｆｇｈｉｊｋｌｍｎｏｐｑｒｓｔｕｖｗｘｙｚＡＢＣＤＥＦＧＨＩＪＫＬＭＮＯＰＱＲＳＴＵＶＷＸＹＺ')
	# pinyin should only contains these characters
	pinyinChars = set(u'abcdefghijklmnopqrstuvwxyz /1234')
	
	def __init__(self, dicItem):
		""" Constructor.
		"""
		self._dicItem = dicItem
		self._children_offset_indexed = None
		self._checked_another_name = False
		self._checked = False

	def check(self, forceCheck = False):
		"""Check dictionary item.
		"""
		if self._checked:
			return True
		self._checked = True
		if not forceCheck:
			try:
				self._dicItem.fetch_kiwi_record()
			except KeyError:
				#log.warning(r'<Check another name> : You should check it manually : %s : %s', self._dicItem.get_father()['nextLevelId'], self._dicItem.get_str())
				return self._check_another_name()
		# check all the items
		r1 = self._check_1()
		r2 = self._check_2()
		r3 = self._check_3()
		r4 = self._check_4()
		r5 = self._check_5()
		r6 = self._check_6()
		r7 = self._check_7()
		r8 = self._check_8()
		r9 = self._check_9()
		return r1 and r2 and r3 and r4 and r5 and r6 and r7 and r8 and r9	
	
	def _check_another_name(self):
		"""Check another name of the dictionary item.
		"""
		self._checked_another_name = True
		for item in self._dicItem.get_father().get_children():
			if item['offset'] == self._dicItem['offset']:
				try:
					item.fetch_kiwi_record()
				except KeyError:
					continue
				if not self.is_another_name(item, self._dicItem):
					log.warning(r'<Check Another Name Error> : not another name : %s', self._dicItem.get_str())
					return False
				log.info(r'<Check another name> : %s : %s : %s', 
						self._dicItem.get_father()['nextLevelId'], 
						self._dicItem.get_str(),
						item.get_str())
				return DicItemChecker(item).check(True)
		log.warning(r'<Check Another Name Error> : %s', self._dicItem.get_str())
		return False
	
	def is_another_name(self, r1, r2):
		"""Check if r1 is another of r2.
		"""
		if r1['nextLevelId'] != r2['nextLevelId'] \
				or r1['nextLevelInfo'] != r2['nextLevelInfo'] \
				or r1['offset'] != r2['offset'] \
				or r1['voiceId'] != r2['voiceId'] \
				or r1['nextLevelNum'] != r2['nextLevelNum']:
			return False
		elif r1['name'].find(r2['name']) >= 0 or r2['name'].find(r1['name']) >= 0:
			return True
		elif r1['name'].find(u'全域') >= 0 and r2['name'].find(u'全域') >= 0:
			return True
		else:
			return False
	
	def checked_another_name(self):
		"""Judge if another name has been checked
		"""
		return self._checked_another_name;
	
	def _check_1(self):
		"""Check the name.
		"""
		return True

	def _check_2(self):
		"""Check pinyin.
		"""
		d = self._dicItem
		r1 = self._check_2_1()
		r2 = self._check_2_2();
		if not r1:
			log.info(r'<Check_2 Error> Alphabet text but not NoPinYin : %s : %s', self._dicItem.get_father()['nextLevelId'], self._dicItem.get_str())
			return False
		if not r2:
			log.info(r'<Check_2 Error> Pinyin contains other characters : %s : %s', self._dicItem.get_father()['nextLevelId'], self._dicItem.get_str())
			return False
		return True

	def _check_3(self):
		"""Check next level id.
		"""
		r1 = self._check_3_1()
		r2 = self._check_3_2()
		return r1 and r2

	def _check_4(self):
		"""Check next level info.
		"""
		if not self._dicItem.has_child():
			if self._dicItem['nextLevelInfo'] != '0x00000001':
				log.info(r'<Check_4 Error> next level info : %s : %s', self._dicItem.get_father()['nextLevelId'], self._dicItem.get_str())
				return False
			return True
		
		ret = True
		getb = lambda x,n: bool(x>>n & 1)
		nextLevelInfo = int(self._dicItem['nextLevelInfo'], 16)
		bits = tuple(getb(nextLevelInfo, n) for n in range(32))
		for n in range(7,32):
			if bits[n] != False:
				log.info('<Check_4 Error> next level info : %s', self._dicItem.get_str())
		# check bit0
		for item in self._dicItem.get_children():
			type = item.get_type()
			if type == 'Sheng' or type == 'ZhiXiaShi':
				if bits[1] != True or bits[6] == True:
					log.warning(r'<Check_4 Error> : Sheng/ZhiXiaShi : %s', self._dicItem.get_str())
					ret = False
			elif type == 'Di':
				if bits[2] != True or bits[6] == True:
					log.warning(r'<Check_4 Error> next level info : Di : %s', self._dicItem.get_str())
					ret = False
			elif type == 'Xian' or type == 'Qu':
				if bits[3] != True or bits[6] == True:
					log.warning(r'<Check_4 Error> next level info : Xian : %s', self._dicItem.get_str())
					ret = False
			elif type == 'Lu':
				if bits[4] != True:
					log.warning(r'<Check_4 Error> next level info : Lu : %s', self._dicItem.get_str())
					ret = False
			elif type == 'QuanYu':
				pass
			else:
				raise KeyError()
		if self._dicItem.get_type() == 'QuanYu':
			if bits[6] != True:
				log.warning(r'<Check_4 Error> next level info : QuanYu Road Check Error : %s', self._dicItem.get_str())
				ret = False
		else:
			if bits[6] == True:
				log.warning(r'<Check_4 Error> next level info : QuanYu Road Check Error : %s', self._dicItem.get_str())
				ret = False
		return ret

	def _check_5(self):
		"""Check offset equals to kiwi data.
		"""
		if int(self._dicItem['offset'], 16) == \
				self._dicItem.fetch_kiwi_record()['offset']:
			return True
		else:
			log.warning(r'<Check_5 Error> offset : %s : %s', self._dicItem.get_father()['nextLevelId'], self._dicItem.get_str())
			return False

	def _check_6(self):
		"""Check voice id.
		"""
		voiceId = int(self._dicItem['voiceId'], 16)
		if voiceId == 0xffffffff:
			voiceId = 0
		kVoiceId = self._dicItem.fetch_kiwi_record()['voiceId']
		if voiceId == kVoiceId:
			return True
		else:
			log.warning(r'<Check_6 Error> voice id : %s', self._dicItem.get_str())
			return False
		return True

	def _check_7(self):
		"""Check the number of next level places.
		"""
		num = int(self._dicItem['nextLevelNum'], 16)
		realNum = num
		ret = True
		if not self._dicItem.has_child():
			# if num != 0 !!
			if num != len(self._dicItem.get_children()):
				ret = False
		else:
			realNum = len(set(r['offset'] for r in self._dicItem.get_children()))
			if num == 0 or realNum == 0 or realNum > num:
				ret = False
		try:
			knum = len(self._dicItem.fetch_kiwi_frame())
		except KeyError:
			knum = 0;
		if realNum != knum:
			ret = False
		if not ret:
			log.warning(r'<Check_7 Error> next level number : %s', self._dicItem.get_str())
		return ret

	def _check_8(self):
		"""Check replacename, which is already checked.
		"""
		return True

	def _check_9(self):
		"""Check others.
		"""
		r1 = self._check_9_1()
		r2 = self._check_9_2()
		r3 = self._check_9_3()
		r4 = self._check_9_4()
		return r1 and r2 and r3 and r4

	def _check_2_1(self):
		"""Check if text has alphabet, then pinyin should be "NoPinYin"
		"""
		def has_alphabet(s):
			for c in s:
				if c in self.alphabets:
					return True
			return False
		if has_alphabet(self._dicItem['name']):
			if self._dicItem['pinyin'] != u'NoPinYin':
				return False
		return True
	
	def _check_2_2(self):
		"""Check characters in pinyin
		"""
		pinyin = self._dicItem['pinyin']
		if pinyin == 'NoPinYin':
			return True
		for c in pinyin:
			if not c in self.pinyinChars:
				return False
		return True
	
	def _check_3_1(self):
		"""Check next level id and if next level file exists
		"""
		d = self._dicItem
		try:
			k = d.fetch_kiwi_record()
		except:
			log.warning(r'<Fetch Kiwi Record Error> : %s', d.get_str())
			return False
		nextLevelId = d['nextLevelId']
		if not k.has_next_level():
			if nextLevelId != '0xFFFFFFFF':
				log.warning(r'<Check_3_1 - Has Next Level Error> : %s', d.get_str())
				# do not return now, go on checking
			return True
		else:
			if nextLevelId == '0xFFFFFFFF':
				log.warning(r'<Check_3_1 - Has Next Level Error> : %s', d.get_str())

		# check file exists
		filename = nextLevelId[2:]+'.txt'
		filepath = os.path.join(d._folder, filename)
		if not os.path.isfile(filepath):
			# file not exist
			log.warning(r'<Check_3_1 - File Not Exists Error> : %s', d.get_str())
			return False
		return True

	def _check_3_2(self):
		"""Check if [next level id] corresponds with higher-level
		"""
		d = self._dicItem
		# road, checked in check_3_1
		if not d.has_child():
			return True
		# province
		type = d.get_type()
		fId = d.get_father()['nextLevelId']
		Id = d['nextLevelId']
		
		ifId = int(fId, 16)
		iId = int(Id, 16)
		if fId != '0x80000001':
			if ifId & iId != ifId or ifId | iId != iId or ifId == iId:
				log.warning(r'<Check_3_2 Error> : %s', d.get_str())

		if type == 'Sheng' or type == 'ZhiXiaShi':
			if re.match(r'0x8[\dA-F]{2}00000', Id) and Id != '0x80000000':
				return True
			else:
				log.warning(r'<Check_3_2 Error> : Sheng/ZhiXiaShi : %s', d.get_str())
				return False
		elif type == 'Di':
			if re.match(fId[:5] + r'[\dA-F]{2}000', Id) and Id != fId:
				return True
			else:
				log.warning(r'<Check_3_2 Error> : Di : %s', d.get_str())
				return False
		elif type == 'Xian' or type == 'Qu':
			if re.match(fId[:7] + r'[\dA-F]{3}', Id) and Id != fId:
				return True
			else:
				log.warning(r'<Check_3_2 Error> : Xian/Qu : %s', d.get_str())
				return False
		elif type == 'QuanYu':
			if Id[-3:] != '001' or Id == fId:
				log.warning(r'<Check_3_2 Error> : QuanYu : %s', d.get_str())
			return True
	
	def _check_9_1(self):
		"""Check if all fields exists in each item.
		Already checked in DicItem#get_children
		"""
		return True
	
	def _check_9_2(self):
		"""Check dictionary format.
		Already checked in DicItem#get_children
		"""
		return True
	
	def _check_9_3(self):
		"""Check if there are repeated next level info or voice id.
		Replace names are checked specially.
		@retval True: no repeated next level id or voice id.
		@retval False: has repeated next level id or voice id.
		"""
		if not self._dicItem.has_child():
			return True
		Rs = self._dicItem.get_children()
		ret = True
		for r in Rs:
			id = r['nextLevelId']
			if id == '0xFFFFFFFF':
				continue
			sameIdRs = filter(lambda rt:rt['nextLevelId']==id, Rs)
			if len(sameIdRs) != 1:
				for r in sameIdRs[1:]:
					if not self.is_another_name(r, sameIdRs[0]):
						log.warning(r'<Next Level Has Repeated Id Error> : %s : %s : %d', self._dicItem.get_str(), id, len(sameIdRs))
						ret = False
		for r in Rs:
			vid = r['voiceId']
			if vid == '0xFFFFFFFF':
				continue
			sameVidRs = filter(lambda rt:rt['voiceId']==vid, Rs)
			if len(sameVidRs) != 1:
				for r in sameVidRs[1:]:
					if not self.is_another_name(r, sameIdRs[0]):
						log.warning(r'<Next Level Has Repeated VoiceId Error> : %s : %s : %d', self._dicItem.get_str(), id, len(sameVidRs))
						ret = False
		return ret
	
	def _check_9_4(self):
		"""Check if all records in Kiwi exists in dictionary
		"""
		ret = True
		d = self._dicItem
		k = d.fetch_kiwi_record()
		nextLevelOffsets = tuple(int(c['offset'], 16) for c in self._dicItem.get_children()) 
		if not k.has_next_level():
			if not d.has_child():
				return True
			log.warning(r'<Check_9_4 Error> : %s', d.get_str())
			return False
		for k in d.fetch_kiwi_frame():
			if not k['offset'] in nextLevelOffsets:
				log.warning(r'<Check_9_4 Error> : %s : %x', self._dicItem.get_str(), k['offset'])
				ret = False
		return ret

